/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include <stdio.h>
#include "cc_compat.h"
#include "chip.h"
#include "power.h"
#include "timer.h"
#include "io.h"
#include "kbc.h"
#include "sdi.h"
#include "keyboard.h"
#include "sdi.h"
#include "touchpad.h"
#include "battery.h"
#include "ac.h"
#include "event.h"
#include "stop.h"

#define DO_PWRUP_RETRY 1
#define SHOW_POWER_STATES 0

static int xdata PowerUpTimer;
static int xdata pwrbut_debounce_timer;
static int xdata min_off_time;
static int xdata pwrbut_debounce_blink_timer;

static unsigned char xdata pwrbut_debounce_state;
static unsigned char xdata PwrButtState;
static unsigned char xdata PwrFlickerCount;
static unsigned char xdata commandPowerOff;
static unsigned char xdata commandPowerUp;
static unsigned char xdata commandPowerCycle;

unsigned char xdata PwrSequenceState;
unsigned char xdata host_pwr_state;

unsigned char xdata game_keys_enable;

unsigned char xdata suspend_hint;


#if SHOW_POWER_STATES
static unsigned char xdata prevPwrSequenceState;
static unsigned char xdata prev_pwrbut_debounce_state;
#endif
/* ------------------------------------------------------------------------- */
#define FLICKER_TIME		100

/* note that these two routines are different, since
 * there is HOST_TRYING_TO_BOOT state as well.
 */
unsigned char power_host_is_on(void)
{
	return (host_pwr_state == HOST_ON);
}

unsigned char power_host_is_off(void)
{
	return (host_pwr_state == HOST_POWERED_OFF);
}

#ifdef UNUSED
unsigned char power_but_in_dbounce(void)
{
	return (pwrbut_debounce_state != 0);
}
#endif

void power_command_powercycle(void)
{
	commandPowerCycle = 1;
}

void power_start_host(void)
{
	commandPowerUp = 1;
}

void power_command_shutdown(void)
{
	commandPowerOff = 1;
}

void autopower_init(void)
{
	unsigned char tag;
	switch (FIRMWARE_ID)
	{
		case EC_RESET_AUTOPOWERON:
			power_start_host();
			break;
	}

	// If AP or CP is set then do those power modes.
	tag = ec_tags[EC_TAG_CP];
	if ( tag == 'C')
	{
		BootFlags |= CONST_PWR_FLAG;
		power_start_host();
		puts("CP enabled");
	}
	tag = ec_tags[EC_TAG_AP];
	if ( tag == 'A')
	{
		BootFlags |= AUTO_PWR_FLAG;
		puts("AP enabled");
	}
}

/*-----------------------------------------------------------------------------
Name : PrepareSystemOnIO
-----------------------------------------------------------------------------*/
void turn_board_on(void)
{
	sdi_init();
	set_LED_PWR_on();
}

// Compatability from prevous code base function
// TODO: PUll in the XO- 1.5 SD power off delay feature

void turn_board_off(void)
{
	// FIXME: Commented out code
	print_tstamp();
	puts("Board Off\n");

	set_LED_PWR_off();
#ifndef NO_PS2
	kbc_id_stop();
#endif
	min_off_time=100;

	disable_EN_DCON_PWR();
	usec_delay(6000);
	disable_EN_MAIN_PWR();
	disable_EN_VCORE_PWR();
	disable_EN_1_8V_PMIC();
	disable_EN_1_8V_GPIO();
	disable_EN_3_3V_SOC();
	disable_EN_3_3V_NAND();
	disable_EN_KBD_PWR();
	disable_EN_1_5V_DDR3();
	disable_EN_1_2V();
	disable_EN_USB_PWR();
	enable_SOC_RESET();

	// TODO: These will probably need the delayed power off stuff backported from
	// 1.5
	disable_EN_SD1_PWR();
	disable_EN_SD2_PWR();

	// This is set this way because of leakage.  With the SoC
	// off all signals fed to it need to be at 0V.

	// With MAIN_PWR disabled the upstream switch is off
	// so the rail does not turn back on.
	enable_EN_1_8V_PMIC();

	sdi_deinit();

	// Disable any outstanding runin modes
	if ( battery_is_in_runin() )
	{
		battery_cmd_runin_discharge_disable();
		battery_set_debug(BATTERY_DEBUG_OFF);
	}
}

#ifdef UNUSED
void set_min_off_time(int val)
{
	min_off_time=val;
}
#endif

// allow override from monitor()
unsigned char soc_sleep_eco_present;

unsigned char suspend_hint;

void set_suspend_hint(unsigned char s)
{
	if (suspend_hint != s)
	{
		suspend_hint = s;
		print_tstamp();
		printf("suspend_hint --> %d\n", (uint)suspend_hint);
	}
}

void check_soc_sleep(void)
{
	static unsigned char last_soc_sleep;
	unsigned char s_s;
	s_s = read_SOC_SLEEP();
	if (last_soc_sleep != s_s)
	{
		print_tstamp();
		printf("SOC_SLEEP --> %d\n", (uint) s_s);
		last_soc_sleep = s_s;
	}
}

// this checks the hardware definition of suspend -- is the SoC
// actually in sleep state?  (i.e., is VCXO_EN low?)
unsigned char host_in_suspend(void)
{
	if (power_host_is_on())
	{
		if (board_rev > 0xb3 || board_rev == 0xa1 || soc_sleep_eco_present)
			return read_SOC_SLEEP();
		else
			return suspend_hint;
	}
	return 0;
}

// this is a broader, software definition of suspend:  the
// interval between getting a command that says the SoC is trying
// to suspend, and any subsequent command from the SoC.
unsigned char host_suspending(void)
{
	if (power_host_is_on())
	{
		return suspend_hint;
	}
	return 0;
}

extern int stop_mode_timer;

void power_handle_suspend(void)
{
	static int suspend_blink_timer;
	static int host_did_suspend;

	if (!host_in_suspend())
	{
		if (host_did_suspend)
		{
			set_LED_PWR_on();
			host_did_suspend = 0;
			suspend_blink_timer = 0;
			power_usb(1);
		}
		stop_mode_timer = 0;
		return;
	}

	host_did_suspend = 1;
	power_usb(0);

	/* if the laptop is unplugged, we go into stop mode, and the
	 * LED blink is handled inside enter_stop_mode().
	 */
	if (!enter_stop_mode(1,1))
	{
		/* if we have external power, we stay running and
		 * blink the power LED once in a while.
		 */

		if (suspend_blink_timer == 0)
		{
			suspend_blink_timer = get_ms_timer();
		}

		if (read_LED_PWR())
		{
			if (check_timer(suspend_blink_timer, 100))
			{
				suspend_blink_timer = 0;
				set_LED_PWR_off();
			}
		}
		else
		{
			if (check_timer(suspend_blink_timer, 4000))
			{
				suspend_blink_timer = 0;
				set_LED_PWR_on();
			}
		}
	}
}

void power_process(void)
{
	/* time base is 10ms for power button checking */
	PwrButtState	= read_PWR_BTN();


	// Watch the status of external power
	ac_process_adapter_status();

#if SHOW_POWER_STATES
	// Debounce the power button
	if (prev_pwrbut_debounce_state != pwrbut_debounce_state)
	{
		printf("pwr_but_debounce_state now %d\n", (uint)pwrbut_debounce_state);
		prev_pwrbut_debounce_state = pwrbut_debounce_state;
	}
#endif
	// Debounce the power button
	switch (pwrbut_debounce_state)
	{
			// Start Button is not asserted.
		case 0:
			// Button depressed (OEMIO returns asserted/not asserted)
			if ( PwrButtState)
			{
				print_tstamp();
				PUTS("Dbounce");
				pwrbut_debounce_timer = get_ms_timer();
				pwrbut_debounce_state = 1;
			}
			break;

		case 1:
			if (!PwrButtState)
			{
				pwrbut_debounce_state = 0;
				break;
			}

			if (check_timer(pwrbut_debounce_timer,150) )
			{
				pwrbut_debounce_state = 2;
				print_tstamp();
				PUTS("PwrButPress assert");
			}
			break;

		case 2:
			// If the system was previously off then power up
			if ( (host_pwr_state == HOST_POWERED_OFF) && (PwrSequenceState == DO_NOTHING) )
			{
				power_start_host();
				pwrbut_debounce_state = 4;
				break;
			}

			// If we are powered up already then move on for further tests
			if (host_pwr_state== HOST_ON || host_pwr_state == HOST_TRYING_TO_BOOT)
			{
				// tell the SoC about it.  it's convenient for
				// the system to be able to tell whether this
				// press caused a wakeup or not.
				if (host_in_suspend())
				{
					event_send_event(EVENT_POWER_PRESS_WAKE);
				}
				else
				{
					event_send_event(EVENT_POWER_PRESSED);
				}

				// Insure the led is on so can do a quck blink in the next state
				set_LED_PWR_on();
				pwrbut_debounce_state = 3;
				break;
			}
			break;

		case 3:
			// Strobe to wake up from suspend
			if (host_pwr_state==HOST_ON)
			{
				set_LED_PWR_off();
				puts("SYSOn PWR_BUT strobe");
			}
			pwrbut_debounce_blink_timer = get_ms_timer();
			pwrbut_debounce_state = 6;
			break;

		case 4:
			if (!PwrButtState)
			{
				pwrbut_debounce_state = 0;
			}
			break;

		case 5:
			if (!PwrButtState)
			{
				pwrbut_debounce_state = 0;
				break;
			}
			// Depressed for a long time?
			// power off.  ACPI says this should be 4 seconds.
#ifdef DO_RELEASE
#define ACPI_OFF_TIME	3700
#else
#define ACPI_OFF_TIME	700
#endif
			if (check_timer(pwrbut_debounce_timer,ACPI_OFF_TIME) )
			{
				print_tstamp();
				puts("PowerButOff");
				PwrSequenceState = START_FINAL_FLICKER;
				pwrbut_debounce_state = 4;
				break;
			}
			break;

		case 6:
			if (check_timer(pwrbut_debounce_blink_timer,100) )
			{
				set_LED_PWR_on();
				pwrbut_debounce_state = 5;
			}
			break;

	}

#if SHOW_POWER_STATES
	if (prevPwrSequenceState != PwrSequenceState)
	{
		printf("PwrSequenceState now %d\n", (uint)PwrSequenceState);
		prevPwrSequenceState = PwrSequenceState;
	}
#endif
	/* power event action */
	switch (PwrSequenceState)
	{
		case DO_NOTHING:
			if (commandPowerUp)
			{
				print_tstamp();
				puts("PwrUP");
				PwrSequenceState = START_POWER_UP;
				commandPowerUp = 0;
				if (FIRMWARE_ID == EC_RESET_AUTOPOWERON)
				{
					FIRMWARE_ID = 0;
				}
				break;
			}

			enter_stop_mode(1,0);
			break;

		case START_POWER_UP:
			// FIXME: Take care of stuff commented out
			PwrSequenceState = ENFORCE_MIN_OFF_TIME;
			PowerUpTimer = get_ms_timer();
			host_pwr_state = HOST_TRYING_TO_BOOT;
			//	WDT_Clear();
			print_tstamp();
			puts("MinOff");
			break;

			// Make sure that we are off for guarenteed ammount of
			// time to let all the power supplies discharge
		case ENFORCE_MIN_OFF_TIME:
			if (check_timer(PowerUpTimer,min_off_time))
			{
				PwrSequenceState = BOARD_ON;
			}
			break;

		case BOARD_ON:

#ifndef NO_PS2
			key_reset();
			kbc_id_start();
#else
			// Due to leakage problems moving full control of the keyboard power up to the host
			// didn't work.  So the EC is retaining control of the power to keyboard and mouse.
			enable_EN_KBD_PWR();
			print_tstamp();
			puts("EN_KBD_PWR");
#endif

			commandPowerOff = 0;
			// All the setup stuff that needs to happen prior to
			// a power up.
			turn_board_on();

			print_tstamp();
			puts("EN Main");
			puts("EN_1.8V PMIC, VCORE, & SD");

			// Leakage concerns has 1.8V PMIC fliped while off
			// Disable it before you turn MAIN_PWR on.
			disable_EN_1_8V_PMIC();
			enable_EN_MAIN_PWR();
			// MAIN_PWR to VCORE timing needs to be controlled tigher than state-to-state
			// timing allows for.  A small 1ms blocking delay here should not cause any
			// troubles.
			usec_delay(1000);
			enable_EN_VCORE_PWR();
			// Quanta power team requested that 1.8V PMIC also be tightly tied to
			// EN_MAIN
			usec_delay(1000);
			enable_EN_1_8V_PMIC();

			enable_EN_SD1_PWR();

			PowerUpTimer = get_ms_timer();
			PwrSequenceState = PRE_EN_1_2V_DELAY;
			break;

		case PRE_EN_1_2V_DELAY:
			if (check_timer(PowerUpTimer,4))
			{
				PwrSequenceState = EN_1_2V;
			}
			break;

		case EN_1_2V:
			enable_EN_1_2V();
			enable_EN_1_5V_DDR3();
			print_tstamp();
			puts("EN_1.2V & DDR3");
			PowerUpTimer = get_ms_timer();

			PwrSequenceState = PRE_EN_1_8V_GPIO_DELAY;
			break;

		case PRE_EN_1_8V_GPIO_DELAY:
			if (check_timer(PowerUpTimer,10))
			{
				PwrSequenceState = EN_1_8V_GPIO;
			}
			break;

		case EN_1_8V_GPIO:
			enable_EN_1_8V_GPIO();
			print_tstamp();
			puts("1.8V GPIO");
			PowerUpTimer = get_ms_timer();
			PwrSequenceState = PRE_REMAINING_RAILS_DELAY;
			break;

		case PRE_REMAINING_RAILS_DELAY:
			if (check_timer(PowerUpTimer,5))
			{
				PwrSequenceState = REMAINING_RAILS;
			}
			break;

		case REMAINING_RAILS:
			enable_EN_3_3V_SOC();
			enable_EN_3_3V_NAND();
			enable_EN_DCON_PWR();
			enable_EN_USB_PWR();
			print_tstamp();
			puts("3.3V SOC & NAND & DCON & USB");
			PwrSequenceState = WAIT_ALL_PWRGOOD;
			break;

		case WAIT_ALL_PWRGOOD:
			if ( read_ALL_PWRGOOD() )
			{
				puts("EN_SD2_PWR");
				enable_EN_SD2_PWR();
				print_tstamp();
				puts("Got ALL_PWRGOOD");
				PowerUpTimer = get_ms_timer();
				PwrSequenceState = PRE_DISABLE_SOC_RESET_DELAY;
			}

			if (check_timer(PowerUpTimer,100))
			{
				print_tstamp();
				puts("ALL_PWRGOOD timeout");
				set_LED_CHG_GREEN_off();
				set_LED_CHG_RED_on();
				usec_delay(200000);
				set_LED_CHG_RED_off();
				usec_delay(200000);
				set_LED_CHG_RED_on();
				usec_delay(200000);
				set_LED_CHG_RED_off();
				turn_board_off();
				PowerUpTimer = get_ms_timer();
				PwrSequenceState =  POWER_OFF_RESTART_DELAY;
			}
			break;

		case PRE_DISABLE_SOC_RESET_DELAY:
			if (check_timer(PowerUpTimer,5))
			{
				print_tstamp();
				PowerUpTimer = get_ms_timer();
				PwrSequenceState = DISABLE_SOC_RESET;
			}
			break;

		case DISABLE_SOC_RESET:
			disable_SOC_RESET();
			PwrSequenceState = WAIT_SYS_RESET;
			PowerUpTimer = get_ms_timer();
			break;

		case WAIT_SYS_RESET:
			if (read_SYS_RESET())
			{
				print_tstamp();
				puts("Got SYS_RESET");
				PowerUpTimer = get_ms_timer();
				PwrSequenceState = SYSTEM_BOOTED;
			}

			if (check_timer(PowerUpTimer,100))
			{
				print_tstamp();
				puts("SYS_RESET timeout");
				set_LED_CHG_GREEN_off();

				set_LED_CHG_RED_on();
				usec_delay(200000);
				set_LED_CHG_RED_off();
				usec_delay(200000);

				set_LED_CHG_RED_on();
				usec_delay(200000);
				set_LED_CHG_RED_off();
				usec_delay(200000);

				set_LED_CHG_RED_on();
				usec_delay(200000);
				set_LED_CHG_RED_off();
				usec_delay(200000);

				set_LED_CHG_RED_on();
				usec_delay(200000);
				set_LED_CHG_RED_off();

				PwrSequenceState =  POWER_OFF_RESTART_DELAY;
			}
			break;

		case NO_BOOT_DELAY:
			if (check_timer(PowerUpTimer,200))
			{
				print_tstamp();
				PUTS("Re-trying");
				PwrSequenceState = BOARD_ON;
				set_LED_CHG_RED_off();
			}
			break;

		case POWER_OFF_RESTART_DELAY:
			if (check_timer(PowerUpTimer,1500))
			{
				print_tstamp();
				PUTS("PWR off restart");
				PwrSequenceState = BOARD_ON;
			}
			break;

		case SYSTEM_BOOTED:
			host_pwr_state = HOST_ON;
			sdi_start();
			print_tstamp();
			PUTS("PwrUp Done");
			PowerUpTimer = get_ms_timer();
			PwrSequenceState = WATCH_FOR_SOFT_OFF;
//			PwrSequenceState = WAIT_POLL_BATTERY;
			game_keys_enable = 1;
//			dump_xdata_sfr();
			break;

		case WAIT_POLL_BATTERY:
			if (check_timer(PowerUpTimer,1000))
			{
				print_tstamp();
				PUTS("Battery Poll");
//				battery_enable_debug();
				PowerUpTimer = get_ms_timer();
				PwrSequenceState = WATCH_FOR_SOFT_OFF;
			}
			break;

		case WATCH_FOR_SOFT_OFF:
			if (commandPowerOff)
			{
				printf("setting start_final_flicker\n");
				PwrSequenceState = START_FINAL_FLICKER;
				commandPowerOff = 0;
			}
			if (commandPowerCycle)
			{
				print_tstamp();
				printf("starting power cycle\n");
				commandPowerUp = 1;
				commandPowerCycle = 0;
				PwrSequenceState = START_FINAL_FLICKER;
			}
			power_handle_suspend();
			break;

		case START_FINAL_FLICKER:
			set_LED_PWR_on(); // in case we were in suspend
			PwrFlickerCount = 4;
			PowerUpTimer = get_ms_timer();
			PwrSequenceState = FINAL_FLICKER_OFF;
			break;

		case FINAL_FLICKER_OFF:
			set_LED_PWR_off();
			if (check_timer(PowerUpTimer,FLICKER_TIME))
			{
				PowerUpTimer = get_ms_timer();
				PwrSequenceState = FINAL_FLICKER_ON;
			}
			break;

		case FINAL_FLICKER_ON:
			set_LED_PWR_on();
			if (check_timer(PowerUpTimer,FLICKER_TIME))
			{
				PwrFlickerCount--;
				PowerUpTimer = get_ms_timer();
				PwrSequenceState = FINAL_FLICKER_OFF;
			}

			if (PwrFlickerCount<=0)
			{
				PwrSequenceState = TURN_OFF;
			}
			break;

		case TURN_OFF:
			PUTS("Turning Power Off\n");
			host_pwr_state = HOST_POWERED_OFF;
			PwrSequenceState = DO_NOTHING;
			turn_board_off();
			break;
	}
}

void power_dcon(unsigned char on)
{
	if (on)
	{
		enable_EN_DCON_PWR();
	}
	else
	{
		disable_EN_DCON_PWR();
	}
}

void power_usb(unsigned char on)
{
	if (on)  enable_EN_USB_PWR();
	else	disable_EN_USB_PWR();
}

void power_sd2(unsigned char on)
{
	if (on)  enable_EN_SD2_PWR();
	else	disable_EN_SD2_PWR();
}

void power_init_module(void)
{
	PwrSequenceState = TURN_OFF;
//	pwrbut_debounce_state = 0;
#if SHOW_POWER_STATES
	prevPwrSequenceState = 0xff;
	prev_pwrbut_debounce_state = 0xff;
#endif
//	game_keys_enable = 0;
	min_off_time=100;
}


